---
title: Transfer Tokens
description:
  Learn how to transfer tokens between token accounts through cross program
  invocations (CPIs) in Anchor.
---

## How to Transfer Tokens

Transferring tokens involves moving tokens from one token account to another
token account that share the same mint. This is done by invoking the
[`transfer_checked`](https://github.com/solana-program/token/blob/main/program/src/instruction.rs#L1235-L1265)
instruction on a token program. Only the address specified as the owner
(authority) of the source token account can transfer tokens out of the account.

<Callout type="info">
  The [Token
  Program](https://github.com/solana-program/token/blob/main/program/src/processor.rs#L229-L343)
  and [Token Extension
  Program](https://github.com/solana-program/token-2022/blob/main/program/src/processor.rs#L290-L561)
  share similar implementations to achieve the same functionality.
</Callout>

## Examples

To transfer tokens through an Anchor program, you need to make a cross program
invocation (CPI) to the `transfer_checked` instruction on either the Token
Program or Token Extension Program.

This means you are invoking the `transfer_checked` instruction on the Token
Program or Token Extension Program from an instruction in your program. Your
program acts as an intermediary, passing along the required accounts,
instruction data, and signatures to the token program.

### Transfer Tokens via CPI

Use the `token_interface::transfer_checked` function make a CPI to either the
Token Program or Token Extension Program. This function requires:

1. The `TransferChecked` struct which specifies the required accounts:

   - `mint` - The mint account specifying the type of token to transfer
   - `from` - The source token account to transfer tokens from
   - `to` - The destination token account to receive the transferred tokens
   - `authority` - The owner of the source token account

2. The `amount` of tokens to transfer, in base units of the token adjusted by
   decimals. (e.g. if the mint has 2 decimals, amount of 100 = 1 token)

```rust title="lib.rs"
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, TokenAccount, TokenInterface, TransferChecked};

declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");

#[program]
pub mod token_example {
    use super::*;

    pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
        let decimals = ctx.accounts.mint.decimals;

        let cpi_accounts = TransferChecked {
            mint: ctx.accounts.mint.to_account_info(),
            from: ctx.accounts.sender_token_account.to_account_info(),
            to: ctx.accounts.recipient_token_account.to_account_info(),
            authority: ctx.accounts.signer.to_account_info(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
        // [!code highlight]
        token_interface::transfer_checked(cpi_context, amount, decimals)?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct TransferTokens<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(mut)]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(mut)]
    pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
    #[account(mut)]
    pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
}
```

At minimum, the following accounts are required:

```rust title="snippet"
#[derive(Accounts)]
pub struct TransferTokens<'info> {
    // The source token account owner
    #[account(mut)]
    pub signer: Signer<'info>,
    // The mint account specifying the type of token
    #[account(mut)]
    pub mint: InterfaceAccount<'info, Mint>,
    // The source token account to transfer tokens from
    #[account(mut)]
    pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
    // The destination token account to receive tokens
    #[account(mut)]
    pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
    // The token program that will process the transfer
    pub token_program: Interface<'info, TokenInterface>,
}
```

Within the instruction logic, use the:

- `TransferChecked` struct to specify the required accounts
- `token_interface::transfer_checked` function to make the CPI

```rust title="snippet"
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
    // Get the number of decimals for this mint
    let decimals = ctx.accounts.mint.decimals;

    // Create the TransferChecked struct with required accounts
    let cpi_accounts = TransferChecked {
        mint: ctx.accounts.mint.to_account_info(),
        from: ctx.accounts.sender_token_account.to_account_info(),
        to: ctx.accounts.recipient_token_account.to_account_info(),
        authority: ctx.accounts.signer.to_account_info(),
    };

    // The program being invoked in the CPI
    let cpi_program = ctx.accounts.token_program.to_account_info();

     // Combine the accounts and program into a "CpiContext"
    let cpi_context = CpiContext::new(cpi_program, cpi_accounts);

    // Make CPI to transfer_checked instruction on token program
    token_interface::transfer_checked(cpi_context, amount, decimals)?;
    Ok(())
}
```

### Transfer Tokens with PDA token owner via CPI

You can create a token account with a PDA as the owner. This allows your program
to transfer tokens from a program controlled token account by "signing" with the
PDA's seeds in the Cross Program Invocation (CPI). This pattern is useful when
you want the program itself to control token transfers based on conditions
defined within the program.

<Tabs items={["Program", "Client"]}>
<Tab value="Program">

```rust title="lib.rs"
use anchor_lang::prelude::*;
use anchor_spl::{
    associated_token::AssociatedToken,
    token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked},
};

declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");

#[program]
pub mod token_example {
    use super::*;

    pub fn create_and_mint_tokens(ctx: Context<CreateAndMintTokens>, amount: u64) -> Result<()> {
        let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];

        let cpi_accounts = MintTo {
            mint: ctx.accounts.mint.to_account_info(),
            to: ctx.accounts.token_account.to_account_info(),
            authority: ctx.accounts.mint.to_account_info(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
        token_interface::mint_to(cpi_context, amount)?;
        Ok(())
    }

    pub fn transfer_tokens(ctx: Context<TransferTokens>) -> Result<()> {
        let signer_seeds: &[&[&[u8]]] = &[&[b"token", &[ctx.bumps.sender_token_account]]];

        let amount = ctx.accounts.sender_token_account.amount;
        let decimals = ctx.accounts.mint.decimals;

        let cpi_accounts = TransferChecked {
            mint: ctx.accounts.mint.to_account_info(),
            from: ctx.accounts.sender_token_account.to_account_info(),
            to: ctx.accounts.recipient_token_account.to_account_info(),
            authority: ctx.accounts.sender_token_account.to_account_info(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
        token_interface::transfer_checked(cpi_context, amount, decimals)?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct CreateAndMintTokens<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init,
        payer = signer,
        mint::decimals = 6,
        mint::authority = mint,
        mint::freeze_authority = mint,
        seeds = [b"mint"],
        bump
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(
        init,
        payer = signer,
        token::mint = mint,
        token::authority = token_account,
        seeds = [b"token"],
        bump
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct TransferTokens<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        mut,
        seeds = [b"mint"],
        bump
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(
        mut,
        token::mint = mint,
        token::authority = sender_token_account,
        seeds = [b"token"],
        bump
    )]
    pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
    #[account(
        init_if_needed,
        payer = signer,
        associated_token::mint = mint,
        associated_token::authority = signer,
        associated_token::token_program = token_program,
    )]
    pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}
```

</Tab>
<Tab value="Client">

```ts title="test.ts"
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { TokenExample } from "../target/types/token_example";
import {
  TOKEN_2022_PROGRAM_ID,
  getAssociatedTokenAddress,
  getMint,
  getAccount,
} from "@solana/spl-token";

describe("token-example", () => {
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.TokenExample as Program<TokenExample>;
  const [mint, mintBump] = anchor.web3.PublicKey.findProgramAddressSync(
    [Buffer.from("mint")],
    program.programId,
  );

  const [token, tokenBump] = anchor.web3.PublicKey.findProgramAddressSync(
    [Buffer.from("token")],
    program.programId,
  );

  it("Is initialized!", async () => {
    const tx = await program.methods
      .createAndMintTokens(new anchor.BN(100))
      .accounts({
        tokenProgram: TOKEN_2022_PROGRAM_ID,
      })
      .rpc({ commitment: "confirmed" });
    console.log("Your transaction signature", tx);

    const mintAccount = await getMint(
      program.provider.connection,
      mint,
      "confirmed",
      TOKEN_2022_PROGRAM_ID,
    );

    console.log("Mint Account", mintAccount);
  });

  it("Mint Tokens", async () => {
    const tx = await program.methods
      .transferTokens()
      .accounts({
        tokenProgram: TOKEN_2022_PROGRAM_ID,
      })
      .rpc({ commitment: "confirmed" });

    console.log("Your transaction signature", tx);

    const associatedTokenAccount = await getAssociatedTokenAddress(
      mint,
      program.provider.publicKey,
      false,
      TOKEN_2022_PROGRAM_ID,
    );

    const recipientTokenAccount = await getAccount(
      program.provider.connection,
      associatedTokenAccount,
      "confirmed",
      TOKEN_2022_PROGRAM_ID,
    );

    const senderTokenAccount = await getAccount(
      program.provider.connection,
      token,
      "confirmed",
      TOKEN_2022_PROGRAM_ID,
    );

    console.log("Recipient Token Account", recipientTokenAccount);
    console.log("Sender Token Account", senderTokenAccount);
  });
});
```

</Tab>
</Tabs>

In this example, the source token account owner is set to a Program Derived
Address (PDA). The PDA is derived using the seed `b"token"`. This means the
program itself controls token transfers out of the token account through this
PDA.

```rust title="snippet"
#[derive(Accounts)]
pub struct CreateAndMintTokens<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init,
        payer = signer,
        mint::decimals = 6,
        mint::authority = mint,
        mint::freeze_authority = mint,
        seeds = [b"mint"],
        bump
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(
        init,
        payer = signer,
        token::mint = mint,
        // [!code word:token_account]
        // [!code highlight]
        token::authority = token_account,
        seeds = [b"token"],
        bump
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
    pub system_program: Program<'info, System>,
}
```

To transfer tokens, the program must "sign" with the PDA by including the seeds
and bump in the CPI context. This is done by passing the seeds and bump to the
`with_signer` method when creating the CPI context.

```rust title="snippet"
pub fn transfer_tokens(ctx: Context<TransferTokens>) -> Result<()> {
    // [!code word:signer_seeds]
    // [!code highlight]
    let signer_seeds: &[&[&[u8]]] = &[&[b"token", &[ctx.bumps.sender_token_account]]];

    let amount = ctx.accounts.sender_token_account.amount;
    let decimals = ctx.accounts.mint.decimals;

    let cpi_accounts = TransferChecked {
        mint: ctx.accounts.mint.to_account_info(),
        from: ctx.accounts.sender_token_account.to_account_info(),
        to: ctx.accounts.recipient_token_account.to_account_info(),
        authority: ctx.accounts.sender_token_account.to_account_info(),
    };
    let cpi_program = ctx.accounts.token_program.to_account_info();
    // [!code highlight:2]
    let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
    token_interface::transfer_checked(cpi_context, amount, decimals)?;
    Ok(())
}
```

<Callout type="info">
  Note in this example the same PDA is used as both the address of the source
  token account and the source token account owner.
</Callout>
